/*!
   \file

   \brief
      Tracks and renders graphics components. Uses OpenGL.

   © 2011 Mark W. Gabby II

   This file is part of the Avion engine.

   The Avion engine is free software: you can redistribute it and/or modify it under the terms of
   the GNU General Public License as published by the Free Software Foundation, either version 3 of
   the License, or (at your option) any later version.

   The Avion engine is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
   without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
   the GNU General Public License for more details.

   You should have received a copy of the GNU General Public License along with the Avion Engine.
   If not, see <http://www.gnu.org/licenses/>.
*/
#include <cmath>
#include <cstdlib>

#include <gl/gl.h>
#include <gl/glu.h>
#include <gl/glext.h>

#include "Systems/Platform/glCP.hpp"

#include "Systems/EntManager/EntManager.hpp"
#include "Systems/Graphics/Graphics.hpp"
#include "Systems/Resource/Resource.hpp"
#include "Systems/ErrorManager/ErrorManager.hpp"
#include "Systems/Platform/Platform.hpp"

#include "Math/Math.hpp"
#include "Math/VectorN.hpp"
#include "Math/Quaternion.hpp"
#include "Util/Color.hpp"
#include "Util/Timer.hpp"
#include "Util/TGA.hpp"

#include "Util/IOutils.hpp"
#include "Components/gcBase.hpp"
#include "Components/gcConstants.hpp"
#include "Components/gcModel.hpp"

Graphics GFX;

Graphics::Graphics()
{
   anistropyLevel = 0;
   curViewportID = 0;
   activeCameraHandle = kNull32;
   activeBackdropHandle = kNull32;
}

void AddGraphicsViewport( const tWindowID WindowID,
                          const uint16_t Width, const uint16_t Height,
                          const float FOVDegrees, const float AspectRatio )
{
   GFX.AddViewport( WindowID, Width, Height, FOVDegrees, AspectRatio );
}

void Graphics::BindCallbacks()
{
   pf::SetAddGraphicsViewportCallback( AddGraphicsViewport );
}

void Graphics::RenderBackdrop()
{
   bool ActiveBackdropExists = false;
   if ( activeBackdropHandle != kNull32 )
   {
      std::list<gcBackdrop *>::iterator curBackdrop;
      for ( curBackdrop = backdropComps.begin(); curBackdrop != backdropComps.end(); ++curBackdrop )
      {
         gcBackdrop * backdropComp = ( *curBackdrop );
         if ( backdropComp != NULL && backdropComp->GetHandle() == activeBackdropHandle )
         {
            if ( backdropComp->IsEnabled() )
            {
               backdropComp->Draw();
            }
            ActiveBackdropExists = true;
         }
      }
   }
   if ( ActiveBackdropExists == false )
   {
      activeBackdropHandle = kNull32;
   }
}

/*!
   \brief
      Sets up the viewpoint of the current camera so that all following things will render from the
      camera's POV.
*/
void Graphics::SetupCameraViewpoint()
{
   bool CameraTransformApplied = false;
   bool ActiveCameraExists = false;
   if ( activeCameraHandle != kNull32 )
   {
      std::list<gcCamera *>::iterator curCamera;
      for ( curCamera = cameraComps.begin(); curCamera != cameraComps.end(); ++curCamera )
      {
         gcCamera * cameraComp = ( *curCamera );
         if ( cameraComp != NULL && cameraComp->GetHandle() == activeCameraHandle )
         {
            if ( cameraComp->IsEnabled() )
            {
               cameraComp->ApplyTransform();
               CameraTransformApplied = true;
            }
            ActiveCameraExists = true;
         }
      }
   }

   // Load a default camera position if none was applied
   if ( CameraTransformApplied == false )
   {
      glMatrixMode( GL_MODELVIEW );
      glLoadIdentity();
   }
   if ( ActiveCameraExists == false )
   {
      activeCameraHandle = kNull32;
   }
}

/*!
    \brief
    The graphics update function draws all the objects and the
    terrain.
*/
void Graphics::SystemUpdate( double Elapsed )
{
   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT );

   RenderBackdrop();

   SetupCameraViewpoint();

   SetLighting();

   std::list<gcModel *>::iterator curModel;

   for ( curModel = modelComps.begin(); curModel != modelComps.end(); ++curModel )
   {
      gcModel * modelComp = ( *curModel );

      // First, make sure it's not NULL
      if ( modelComp )
      {
         // If it's enabled:
         if ( modelComp->IsEnabled() )
         {
            // Draw it!
            modelComp->Draw();
         }
      }
   }

   std::list<gcCircle *>::iterator curCircle;

   for ( curCircle = circleComps.begin(); curCircle != circleComps.end(); ++curCircle )
   {
      const gcCircle * const circleComp = ( *curCircle );

      if ( circleComp )
      {
         if ( circleComp->IsEnabled() )
         {
            circleComp->Draw();
         }
      }
   }

   std::list<gcSphere *>::iterator curSphere;

   for ( curSphere = sphereComps.begin(); curSphere != sphereComps.end(); ++curSphere )
   {
      const gcSphere * const sphereComp = ( *curSphere );

      if ( sphereComp )
      {
         if ( sphereComp->IsEnabled() )
         {
            sphereComp->Draw();
         }
      }
   }

   // Set the update timestep for particle systems
   gcParticles::sTimeStep = ( float )Elapsed;
   std::list<gcParticles *>::iterator curParticleSystem;

   BeginParticleRendering();

   particleSystemComps.sort( IsFirstPSFarther );
   for ( curParticleSystem = particleSystemComps.begin(); curParticleSystem != particleSystemComps.end(); ++curParticleSystem )
   {
      gcParticles * particleSystemComp = ( *curParticleSystem );

      // First, make sure it's not NULL
      if ( particleSystemComp )
      {
         // If it's enabled:
         if ( particleSystemComp->IsEnabled() )
         {
            // Draw it!
            particleSystemComp->Draw();
         }
      }
   }

   EndParticleRendering();

   BeginOrthoRendering();

   textAndQuadComps.sort( IsFirstFarther );
   std::list<gcBase *>::iterator cur2DComp;

   for ( cur2DComp = textAndQuadComps.begin(); cur2DComp != textAndQuadComps.end(); ++cur2DComp )
   {
      gcBase * GraphicsComponent = *cur2DComp;

      if ( GraphicsComponent->IsEnabled() )
      {
         GraphicsComponent->Draw();
      }
   }

   EndOrthoRendering();

   pf::SwapBuffers( curViewportID );
}

/*!
    \brief
    This function sets up OpenGL to
    begin drawing.
*/
void Graphics::SetOpenGLOptions()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   // Background color
   glClearColor( 0, 0, 0, 1.0f );

   // Misc. OpenGL options
   // Z-Buffer enabled
   glEnable( GL_DEPTH_TEST );
   // Backface culling
   glEnable( GL_CULL_FACE );
   // Rescale vertex normals if objects are scaled
   glEnable( GL_NORMALIZE );

   // Set default lighting options
   SetLighting();

   // Hardcoded material - for testing purposes
   glEnable( GL_COLOR_MATERIAL );
   glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE );

   Color<> Specular( 1.0f, 1.0f, 1.0f, 1.0f );
   glMaterialfv( GL_FRONT, GL_SPECULAR, &Specular );
   glMateriali( GL_FRONT, GL_SHININESS, 8 );

   glEnable( GL_TEXTURE_2D );
   glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

   glShadeModel( GL_SMOOTH );

   // Viewport and projection matrix
   glViewport( 0, 0, Viewport.dimensionsPixels[kWidth], Viewport.dimensionsPixels[kHeight] );

   LoadProjectionMatrix();

   // The engine uses vertex, normal, color, and texture coordinate arrays for models
   glEnableClientState( GL_VERTEX_ARRAY );
   glEnableClientState( GL_NORMAL_ARRAY );
   glEnableClientState( GL_COLOR_ARRAY );
   glEnableClientState( GL_TEXTURE_COORD_ARRAY );

   if ( CrossPlatformOpenGL::ArePointSpritesSupported() )
   {
      GLfloat attenuations[] = { 0, 0, 1.0f };
      CrossPlatformOpenGL::glPointParameterfv( GL_POINT_DISTANCE_ATTENUATION, attenuations );
      glEnable( GL_POINT_SPRITE );
      glTexEnvi( GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE );
   }
}

void Graphics::SetLighting()
{
   // Hardcoded light. For testing purposes...
   glEnable( GL_LIGHTING );
   Color<> Ambient( .2f, .2f, .2f, 1.0f );
   glLightModelfv( GL_LIGHT_MODEL_AMBIENT, &Ambient );

   Color<> Diffuse( .7f, .7f, .7f, 1.0f );
   Color<> Specular( 1.0f, 1.0f, 1.0f, 1.0f );

   glLightfv( GL_LIGHT0, GL_AMBIENT, &Ambient );
   glLightfv( GL_LIGHT0, GL_DIFFUSE, &Diffuse );
   glLightfv( GL_LIGHT0, GL_SPECULAR, &Specular );

   Point4 Position( -1.0f, 1.0f, .1f, 1.0f );

   glLightfv( GL_LIGHT0, GL_POSITION, &Position );

   glEnable( GL_LIGHT0 );
}

void Graphics::LoadProjectionMatrix()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   glMatrixMode( GL_PROJECTION );
   glPushMatrix();
   glLoadIdentity();
   gluPerspective( Viewport.fovDegrees, Viewport.aspectRatio, kNearClipPlane, kFarClipPlane );
}

/*!
    \brief
    This function initializes the graphics system.
*/
bool Graphics::Initialize()
{
   SetOpenGLOptions();

   return true;
}

/*!
   \brief
      Gives Graphics some knowledge of a viewport that can be used for rendering.
   \param ViewportWindowID
      The Window ID of the viewport whose window is being set up.
   \param WidthPixels
   \param HeightPixels
   \param FOVDegrees
   \param CustomAspectRatio
*/
bool Graphics::AddViewport( tWindowID ViewportWindowID,
                            uint16_t WidthPixels, uint16_t HeightPixels,
                            float CustomFOVDegrees, float CustomAspectRatio )
{
   ViewportInfoStruct & NewViewport = viewports[ViewportWindowID];

   NewViewport.dimensionsPixels[kWidth] = WidthPixels;
   NewViewport.dimensionsPixels[kHeight] = HeightPixels;

   // If the aspect ratio hasn't been overriden to some constant value, then recalculate it.
   if ( CustomAspectRatio == 0 )
   {
      NewViewport.aspectRatio = ( float )( WidthPixels / ( double )( HeightPixels ) );
   }
   else
   {
      NewViewport.aspectRatioOverrideActive = true;
      NewViewport.aspectRatio = CustomAspectRatio;
   }

   if ( CustomFOVDegrees == 0 )
   {
      NewViewport.fovDegrees = 70;
   }
   else
   {
      NewViewport.fovDegrees = CustomFOVDegrees;
   }

   NewViewport.widthScreenUnits = NewViewport.aspectRatio * kScreenHeightScreenUnits;
   NewViewport.heightScreenUnits = kScreenHeightScreenUnits;

   NewViewport.leftExtentScreenUnits = -( NewViewport.widthScreenUnits / 2.0f );
   NewViewport.rightExtentScreenUnits = -NewViewport.leftExtentScreenUnits;

   NewViewport.topExtentScreenUnits = ( kScreenHeightScreenUnits / 2.0f );
   NewViewport.bottomExtentScreenUnits = -NewViewport.topExtentScreenUnits;

   NewViewport.screenUnitsPerPixel[kWidth] = ( float )( NewViewport.widthScreenUnits ) / ( double )( WidthPixels );
   NewViewport.screenUnitsPerPixel[kHeight] = ( float )( NewViewport.heightScreenUnits ) / ( double )( HeightPixels );

   return true;
}

bool Graphics::SetViewport( tWindowID ViewportWindowID )
{
   if ( viewports.find( ViewportWindowID ) != viewports.end() )
   {
      curViewportID = ViewportWindowID;
      return true;
   }

   return false;
}

float Graphics::GetAnisotropyLevel()
{
   return anistropyLevel;
}


uint32_t Graphics::GetScreenWidthPx()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   return Viewport.dimensionsPixels[kWidth];
}

uint32_t Graphics::GetScreenHeightPx()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   return Viewport.dimensionsPixels[kHeight];
}

float Graphics::GetScreenWidth()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   return Viewport.widthScreenUnits;
}

float Graphics::GetScreenHeight()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   return Viewport.heightScreenUnits;
}

float Graphics::GetScreenAspectRatio()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   return Viewport.aspectRatio;
}

float Graphics::GetScreenLeftExtent()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   return Viewport.leftExtentScreenUnits;
}

float Graphics::GetScreenRightExtent()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   return Viewport.rightExtentScreenUnits;
}

float Graphics::GetScreenBottomExtent()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   return Viewport.bottomExtentScreenUnits;
}

float Graphics::GetScreenTopExtent()
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   return Viewport.topExtentScreenUnits;
}

/*!
    \brief
    Returns the number of screen units per pixel in either the X or Y directions.
    The screen is GetScreenHeight screen units high and GetScreenWidth screen units wide.
*/
float Graphics::GetNumScreenUnitsPerPixel( eDimensionIndices Dimension )
{
   ViewportInfoStruct Viewport = viewports[curViewportID];

   return Viewport.screenUnitsPerPixel[Dimension];
}

Point3 Graphics::GetScreenWorldSpaceCoords( const Point2 & ScreenLocation, const float WorldSpaceDepth )
{
   Point2 PixelCoords = ScreenToPixelCoords( ScreenLocation );

   Matrix4x4<double> Identity;

   LoadProjectionMatrix();
   Matrix4x4<double> Projection;
   glGetDoublev( GL_PROJECTION_MATRIX, &Projection );
   glPopMatrix();
   PointN<4, int> Viewport;
   glGetIntegerv( GL_VIEWPORT, &Viewport );

   // Find the world coordinates of the screen plane at this mouse position and the world
   // coordinates of a point at this mouse position and -1 units into the screen
   PointN<3, double> ScreenWorldCoords;

   // The problem here is that the window Z coordinate is assumed by gluUnproject to be a value
   // from 0 to 1.
   PointN<4, double> ProjectedZ = PointN<4, double>( 0, 0, 2.0 * WorldSpaceDepth, 1.0 ) * Projection;
   double DepthValue = ProjectedZ.z() / ProjectedZ.w();

   gluUnProject( PixelCoords.x(), PixelCoords.y(), DepthValue, &Identity, &Projection, &Viewport,
                 &ScreenWorldCoords.x(), &ScreenWorldCoords.y(), &ScreenWorldCoords.z() );

   return PointN<3>( ScreenWorldCoords.x(), ScreenWorldCoords.y(), ScreenWorldCoords.z() );
}

Point3 Graphics::GetVectorIntoScreen( Vector3 & IntoScreen, const Point2 & ScreenLocation )
{
   Point2 PixelCoords = ScreenToPixelCoords( ScreenLocation );

   Matrix4x4<double> Identity;

   LoadProjectionMatrix();
   Matrix4x4<double> Projection;
   glGetDoublev( GL_PROJECTION_MATRIX, &Projection );
   glPopMatrix();
   PointN<4, int> Viewport;
   glGetIntegerv( GL_VIEWPORT, &Viewport );

   // Find the world coordinates of the screen plane at this mouse position and the world
   // coordinates of a point at this mouse position and -1 units into the screen
   PointN<3, double> VectorEnd;
   PointN<3, double> ScreenWorldCoords;

   // The problem here is that the window Z coordinate is assumed by gluUnproject to be a value
   // from 0 to 1.
   PointN<4, double> ProjectedZ = PointN<4, double>( 0, 0, -1.0, 1.0 ) * Projection;
   double DepthValue = ProjectedZ.z() / ProjectedZ.w();

   gluUnProject( PixelCoords.x(), PixelCoords.y(), 0, &Identity, &Projection, &Viewport,
                 &ScreenWorldCoords.x(), &ScreenWorldCoords.y(), &ScreenWorldCoords.z() );
   gluUnProject( PixelCoords.x(), PixelCoords.y(), DepthValue, &Identity, &Projection, &Viewport,
                 &VectorEnd.x(), &VectorEnd.y(), &VectorEnd.z() );

   // Use this to create a vector which, when scaled and added to the point on the screen plane, will get a world position
   // at the mouse position at any depth into the screen.
   VectorN<3, double> IntoScreen2 = VectorEnd - ScreenWorldCoords;
   IntoScreen = Vector3( IntoScreen2.x(), IntoScreen2.y(), IntoScreen2.z() );

   return PointN<3>( ScreenWorldCoords.x(), ScreenWorldCoords.y(), ScreenWorldCoords.z() );
}


/*!
    \brief
    Takes a point in pixel coordinates and transforms it into screen coordinates.

    \note
    The origin of the pixel coordinates is assumed to be in the bottom-left, with values positive
    in the X to the right and positive Y up.
*/
Point2 Graphics::PixelToScreenCoords( const float PosX, const float PosY )
{
   return PointN<2>( ( ( PosX / GetScreenWidthPx() ) - 0.5f ) * GetScreenWidth(),
                     ( ( PosY / GetScreenHeightPx() ) - 0.5f ) * GetScreenHeight() );
}

/*!
    \brief
    Takes a point in screen coordinates and transforms it into pixel coordinates.

    \note
    The origin of the pixel coordinates is assumed to be in the bottom-left, with values positive
    in the X to the right and positive Y up.
*/
Point2 Graphics::ScreenToPixelCoords( const Point2 & ScreenCoords )
{
   return PointN<2>( ( ( ScreenCoords.x() / GetScreenWidth() ) + 0.5f ) * GetScreenWidthPx(),
                     ( ( ScreenCoords.y() / GetScreenHeight() ) + 0.5f ) * GetScreenHeightPx() );
}

void Graphics::SetActiveCamera( const uint32_t NewActiveCameraHandle )
{
   activeCameraHandle = NewActiveCameraHandle;
}

void Graphics::SetActiveBackdrop( const uint32_t NewActiveBackdropHandle )
{
   activeBackdropHandle = NewActiveBackdropHandle;
}

const Point3 Graphics::GetCameraPosition()
{
   if ( activeCameraHandle != kNull32 )
   {
      std::list<gcCamera *>::iterator curCamera;
      for ( curCamera = cameraComps.begin(); curCamera != cameraComps.end(); ++curCamera )
      {
         gcCamera * cameraComp = ( *curCamera );
         if ( cameraComp != NULL && cameraComp->GetHandle() == activeCameraHandle )
         {
            return cameraComp->GetAbsolutePosition();
         }
      }
   }
   return Point3( 0, 0, 0 );
}

const Matrix Graphics::GetCameraOrientation()
{
   if ( activeCameraHandle != kNull32 )
   {
      std::list<gcCamera *>::iterator curCamera;
      for ( curCamera = cameraComps.begin(); curCamera != cameraComps.end(); ++curCamera )
      {
         gcCamera * cameraComp = ( *curCamera );
         if ( cameraComp != NULL && cameraComp->GetHandle() == activeCameraHandle )
         {
            return cameraComp->GetAbsoluteOrientation();
         }
      }
   }
   return Matrix();
}

void Graphics::SaveScreenShot( const char * const Name )
{
   uint8_t * Data = new uint8_t[GetScreenWidthPx() * GetScreenHeightPx() * 4];
   glReadPixels( 0, 0, GetScreenWidthPx(), GetScreenHeightPx(), GL_RGBA, GL_UNSIGNED_BYTE, ( void * )Data );

   std::string PathName( "" );
   CONSTANTS.Get( PathName, "ScreenshotsFolder" );
   PathName += Name;
   PathName += ".tga";

   SaveTGA( PathName.c_str(), GetScreenWidthPx(), GetScreenHeightPx(), Data );
   delete[] Data;
}

void Graphics::SystemClose()
{
}

uint32_t Graphics::AddConcreteComponent( gcModel * NewModel )
{
   modelComps.push_back( NewModel );

   return AddComponent( NewModel );
}

uint32_t Graphics::AddConcreteComponent( gcParticles * NewParticleSystem )
{
   particleSystemComps.push_back( NewParticleSystem );

   return AddComponent( NewParticleSystem );
}

uint32_t Graphics::AddConcreteComponent( gcText * NewTextComp )
{
   textComps.push_back( NewTextComp );
   textAndQuadComps.push_back( NewTextComp );

   return AddComponent( NewTextComp );
}

uint32_t Graphics::AddConcreteComponent( gcQuad * NewQuadComp )
{
   quadComps.push_back( NewQuadComp );
   textAndQuadComps.push_back( NewQuadComp );

   return AddComponent( NewQuadComp );
}

uint32_t Graphics::AddConcreteComponent( gcCircle * NewCircleComp )
{
   circleComps.push_back( NewCircleComp );

   return AddComponent( NewCircleComp );
}

uint32_t Graphics::AddConcreteComponent( gcSphere * NewSphereComp )
{
   sphereComps.push_back( NewSphereComp );

   return AddComponent( NewSphereComp );
}

uint32_t Graphics::AddConcreteComponent( gcCamera * NewCameraComp )
{
   cameraComps.push_back( NewCameraComp );

   return AddComponent( NewCameraComp );
}

uint32_t Graphics::AddConcreteComponent( gcBackdrop * NewBackdropComp )
{
   backdropComps.push_back( NewBackdropComp );

   return AddComponent( NewBackdropComp );
}


void Graphics::RemoveComponentFromSpecificLists( const Component * ToRemove )
{
   // TODO: Add all other graphics component lists removal here.
   if ( RemoveComponentFromList( modelComps, ToRemove ) )
   {
      return;
   }

   if ( RemoveComponentFromList( particleSystemComps, ToRemove ) )
   {
      return;
   }

   if ( RemoveComponentFromList( quadComps, ToRemove ) )
   {
      RemoveComponentFromList( textAndQuadComps, ToRemove );
      return;
   }

   if ( RemoveComponentFromList( textComps, ToRemove ) )
   {
      RemoveComponentFromList( textAndQuadComps, ToRemove );
      return;
   }

   if ( RemoveComponentFromList( circleComps, ToRemove ) )
   {
      return;
   }

   if ( RemoveComponentFromList( sphereComps, ToRemove ) )
   {
      return;
   }

   if ( RemoveComponentFromList( cameraComps, ToRemove ) )
   {
      return;
   }

   if ( RemoveComponentFromList( backdropComps, ToRemove ) )
   {
      return;
   }
}

void Graphics::BeginParticleRendering()
{
   glPushClientAttrib( GL_CLIENT_ALL_ATTRIB_BITS );
   glDisableClientState( GL_NORMAL_ARRAY );
   glDisableClientState( GL_TEXTURE_COORD_ARRAY );
   glPushAttrib( GL_ALL_ATTRIB_BITS );
   glDisable( GL_LIGHTING );
   glDepthMask( GL_FALSE );
   glEnable( GL_TEXTURE_2D );
   glEnable( GL_BLEND );
   glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
}

void Graphics::EndParticleRendering()
{
   glPopClientAttrib();
   glPopAttrib();
}

/*!
    \brief
    Initializes rendering for orthographic drawing, such as text and 2D shapes.
*/
void Graphics::BeginOrthoRendering()
{
   glEnable( GL_BLEND );
   glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

   // 2D graphics are drawn "over" everything else.
   glDisable( GL_DEPTH_TEST );

   glDisable( GL_LIGHTING );
   glDisable( GL_CULL_FACE );
   glMatrixMode( GL_PROJECTION );
   glPushMatrix();
   glLoadIdentity();

   gluOrtho2D( GetScreenLeftExtent(), GetScreenRightExtent(),
               GetScreenBottomExtent(), GetScreenTopExtent() );

   glMatrixMode( GL_MODELVIEW );
   glPushMatrix();
   glLoadIdentity();
}

/*!
    \brief
    Resets rendering back to previous state after rendering in orthographic mode.
*/
void Graphics::EndOrthoRendering()
{
   glDisable( GL_BLEND );

   glEnable( GL_DEPTH_TEST );
   glMatrixMode( GL_PROJECTION );
   glPopMatrix();
   glMatrixMode( GL_MODELVIEW );
   glPopMatrix();

   glEnable( GL_CULL_FACE );
   glEnable( GL_LIGHTING );
}

/*!
    \brief
    Enables states for text drawing, a type of orthographic rendering.
*/
void Graphics::BeginTextRendering()
{
   glEnable( GL_TEXTURE_2D );
}

/*!
    \brief
    Reverts states for text drawing, a type of orthographic rendering.
*/
void Graphics::EndTextRendering()
{
   glDisable( GL_TEXTURE_2D );
}

bool Graphics::IsFirstFarther( gcBase * Comp1, gcBase * Comp2 )
{
   return Comp1->GetRenderIndex() > Comp2->GetRenderIndex();
}

bool Graphics::IsFirstPSFarther( gcParticles * Comp1, gcParticles * Comp2 )
{
   Point3 C1Pos = Comp1->GetAbsolutePosition();
   Point3 C2Pos = Comp2->GetAbsolutePosition();

   Point3 CameraPos = GFX.GetCameraPosition();

   Vector3 ToC1 = CameraPos - C1Pos;
   Vector3 ToC2 = CameraPos - C2Pos;

   const float ToC1Sq = ToC1 * ToC1;
   const float ToC2Sq = ToC2 * ToC2;

   if ( FloatEpsilonEquals( ToC1Sq, ToC2Sq ) )
   {
      return false;
   }

   return  ToC1Sq > ToC2Sq;
}
